package org.hawk.gwt.ppc.reflect.emul.java.lang.reflect;

import org.hawk.gwt.ppc.reflect.lang.ClassDelegate;
import org.hawk.gwt.ppc.reflect.lang.GWTClassType;
import org.hawk.gwt.ppc.reflect.lang.GWTTypeVariable;
/**
 * Implementation of java.lang.reflect.Method class for GWT.
 * Clients should never instantiate this class.
 * 
 * @author alex.bereznevatiy@gmail.com
 */
public final class Method extends AccessibleObject implements GenericDeclaration, Member {
	
	private Object accessor;
	private ClassDelegate<?> clazz;
	private String signature;
	private GWTTypeVariable<Method>[] typeParameters;
	private boolean varAgrs;

	public Method(Object accessor, ClassDelegate<?> clazz, String signature, boolean varAgrs) {
		checkAccess(accessor);
		this.accessor = accessor;
		this.clazz = clazz;
		this.signature = signature;
		this.typeParameters = clazz.getMethodTypeParameters(this, accessor, signature);
		this.varAgrs = varAgrs;
	}

	@Override
	public int getModifiers() {
		return clazz.getModifiers(accessor, signature);
	}

	public Class<?> getDeclaringClass() {
		return clazz.toClass();
	}

	public String getName() {
		int i = signature.indexOf('(');
		if (i < 0)
			return signature;
		return signature.substring(0, i);
	}

	public Class<?> getReturnType() {
		return clazz.getReturnType(accessor, signature);
	}

	public Class<?>[] getParameterTypes() {
		return ClassDelegate.unmarshalSignature(signature);
	}

	public ClassDelegate<?>[] getExceptionTypes() {
		throw new UnsupportedOperationException();
	}
	
	public Type[] getGenericParameterTypes() {
		Type[] result = clazz.getMethodGenericParameterTypes(this, accessor, signature);
		if (result == null) {
			thereIsNoGenericSupport();
		}
		if (result.length != 0) {
			return result;
		}
		Class<?>[] params = getParameterTypes();
		result = new Type[params.length];
		for (int i = 0; i < result.length; i++) {
			result[i] = ClassDelegate.forClass(params[i]);
		}
		return result;
	}
	
	public Type getGenericReturnType() {
		if (this.typeParameters == null) {
			thereIsNoGenericSupport();
		}
		Type type = clazz.getGenericReturnType(this, accessor, signature);
		if (type == null) {
			return ClassDelegate.forClass(getReturnType());
		}
		return type;
	}

	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (obj == this)
			return true;
		if (obj instanceof Method) {
			Method m = (Method) obj;
			return m.signature.equals(signature);
		}
		return false;
	}

	public int hashCode() {
		return signature.hashCode();
	}

	public String toString() {
		String mod = Modifier.toString(getModifiers());
		if (!mod.isEmpty()) {
			mod += ' ';
		}
		return mod + getReturnType().getName() + ' ' + clazz.getName() + '.' + signature;
	}
	
	public String toGenericString() {
		StringBuilder result = new StringBuilder(Modifier.toString(getModifiers()));
		if (result.length() != 0) {
			result.append(' ');
		}
		TypeVariable<Method>[] vars = getTypeParameters();
		if (vars.length > 0) {
			result.append('<');
			for (int i = 0; i < vars.length; i++) {
				if (i > 0) {
					result.append(',');
				}
				result.append(vars[i]);
			}
			result.append("> ");
		}
		Type returnType = getGenericReturnType();
		if (returnType instanceof GWTClassType) {
			result.append(((GWTClassType<?>)returnType).toClass().getName());
		} else {
			result.append(returnType);
		}
		result.append(' ');
		result.append(clazz.getName());
		result.append('.');
		result.append(getName());
		result.append('(');
		Type[] params = getGenericParameterTypes();
		for (int i = 0; i < params.length; i++) {
			if (i > 0) {
				result.append(',');
			}
			if (params[i] instanceof GWTClassType) {
				result.append(((GWTClassType<?>)params[i]).toClass().getName());
			} else {
				result.append(params[i]);
			}
		}
		result.append(')');
		return result.toString();
	}

	public Object invoke(Object obj, Object... parameters)
			throws IllegalAccessException,
			java.lang.reflect.InvocationTargetException {
		checkAccess();
		checkParameters(parameters, getParameterTypes());
		if (obj != null && !clazz.isInstance(obj)) {
			throw new IllegalArgumentException(obj + " is not instance of "
					+ clazz);
		}
		try {
			return clazz.invoke(accessor, signature, obj, parameters);
		} catch (Throwable ex) {
			throw new java.lang.reflect.InvocationTargetException(ex);
		}
	}

	public boolean isBridge() {
		return false;
	}

	public boolean isVarArgs() {
		return varAgrs;
	}

	public boolean isSynthetic() {
		return false;
	}

	public boolean getAnnotation(Class<?> cls) {
		throw new UnsupportedOperationException();
	}

	public boolean getDeclaredAnnotations() {
		throw new UnsupportedOperationException();
	}

	public boolean getDefaultValue() {
		throw new UnsupportedOperationException();
	}

	public boolean getParameterAnnotations() {
		throw new UnsupportedOperationException();
	}

	@SuppressWarnings("unchecked")
	public TypeVariable<Method>[] getTypeParameters() {
		if (this.typeParameters == null) {
			thereIsNoGenericSupport();
		}
		TypeVariable<Method>[] result = (TypeVariable<Method>[]) new TypeVariable<?>[this.typeParameters.length];
		for (int i = 0; i < this.typeParameters.length; i++) {
			result[i] = (TypeVariable<Method>) this.typeParameters[i];
		}
		return result;
	}

	static void checkParameters(Object[] parameters, Class<?>[] parameterTypes) {
		if (parameters.length != parameterTypes.length) {
			throw new IllegalArgumentException("Wrong number of parameters: expected "
							+ parameterTypes.length + " but was " + parameters.length);
		}
		for (int i = 0; i < parameters.length; i++) {
			if (parameters[i] == null) {
				if (parameterTypes[i].isPrimitive()) {
					throw new IllegalArgumentException(
							"Unable to cast NULL to " + parameterTypes[i]);
				}
			} else {
				ClassDelegate<?> delegate = ClassDelegate.forClass(parameterTypes[i]);
				if (!delegate.isInstance(parameters[i])) {
					throw new IllegalArgumentException("Unable to cast "
							+ parameters[i].getClass() + " to " + parameterTypes[i]);
				}
			}

		}
	}
}
